Mathematical background:¶
The following works over any field $F$. We will focus on the mathematical field $\mathbb R$ and its subfields (the rationals $\mathbb Q$, number fields, and the field of algebraic reals). The programming will focus on subfields such as these where we can do exact arithmetic.
Points: Given a field $F$, the projective plane $F{\mathbb P}^2$ is the collection of all projective points. A projective point (or simply point) in $F{\mathbb P}^2$ is a line through the origin in $F^3$. As such, a point is an equivalence class in $F^3 \setminus \{\mathbb 0\}$: A set of non-zero scalar multiples of a non-zero vector in $F^3$. For a non-zero vector $\mathbf v \in F^3$, let us write $[\mathbf v]$ for its equivalence class. If $\mathbf v=(x,y,z)$, we write $[\mathbf v] = [x,y,z]$.
Lines: A projective line (or line) is determined by a plane through the origin in $F^3$, and consists of all points (lines through the origin) contained in the plane. A plane through the origin in $F^3$ is determined by a non-zero normal vector ${\mathbf n} \in F^3$. Let $\ell_{\mathbf n}$ denote the line determined by the normal vector $\mathbf n$. Then a point $[\mathbf v] \in F{\mathbb P}^2$ lies in $\ell_{\mathbf n}$ if and only if the dot product $\mathbf n \cdot \mathbf v$ is zero.
Relation to the usual plane: There is a natural inclusion of the plane $F^2$ into $F{\mathbb P}^2$. This map sends $(x,y)$ to the projective point $[x,y,1]$. A point $[x,y,z]$ is in the image of this inclusion if and only if $z \neq 0$. The complement of this image is called the line at infinity. It is given by $\ell_{(0,0,1)}$. Every other line in the projective plane intersects the image of $F^2$, and corresponds to a line in the usual plane $F^2$, with one point added at infinity. All the lines of slope $m \in F$ in $F^2$ extend in $F{\mathbb P}^2$ to include the point at infinity $[1, m, 0]$ (and the vertical lines in $F^2$ extend to include $[0, 1, 0]$).
Joining points: Recall that the cross product $\mathbf v \times \mathbf w$ is a vector orthogonal to both $\mathbf v$ and $\mathbf w$ and is non-zero unless $\{\mathbf v, \mathbf w\}$ is linearly dependent (which would mean either at least one vector is zero, or otherwise $[\mathbf v]=[\mathbf w]$). Consequiently, as long as $[\mathbf v] \neq [\mathbf w]$, the line through these two points is given by $\ell_{\mathbf v \times \mathbf w}$.
Intersecting lines: If $\ell_{\mathbf n}$ is not the same line as $\ell_{\mathbf m}$, then the point they share is given by $[\mathbf n \times \mathbf m]$.
The classes¶
Your task is to manage the classes below (adding methods and improving methods) so that the criteria written further below are satisfied.
The ProjectivePlane class¶
class ProjectivePlane:
def __init__(self, field):
self._field = field
self._vector_space = VectorSpace(field, 3)
def field(self):
'Return the field over which this projective plane is defined.'
return self._field
def vector_space(self):
'Return the vector space F^3 where F is the base field.'
return self._vector_space
def plane(self):
'Return the ProjectivePlane containing this point'
return self._plane
def __repr__(self):
return f'ProjectivePlane({repr(self._field)})'
def __str__(self):
return f'Projective plane over {str(self._field)}'
def __eq__(self, other):
if type(self) != type(other):
return False
# Now we know the objects are both ProjectivePlanes
return self.field() == other.field()
def __hash__(self):
# Return the hash of the pair consisting of 'ProjectivePlane' and the field:
return hash( (ProjectivePlane, self._field()) )
def __contains__(self, item):
# Currently the only thing in a ProjectivePlane is a ProjectivePoint
# Later we will check other types (like Lines).
if isinstance(item, ProjectivePoint):
return self == item.plane()
return False
The ProjectivePoint class¶
class ProjectivePoint:
def __init__(self, plane, v):
'Construct a point from a ProjectivePlane and a 3-dimensional vector.'
# Raise an error if ProjectivePlane is not a ProjectivePlane
if not isinstance(plane, ProjectivePlane):
# Raise a TypeError which indicates that the parameter was the wrong type
raise TypeError('The parameter plane must be a ProjectivePlane')
self._plane = plane # Store the projective plane
v = self._plane.vector_space()(v) # Convert v into the vector space.
if v == self._plane.vector_space().zero():
# Raise a ValueError indicating the vecotr is unacceptable
raise ValueError('The vector can not be zero')
v.set_immutable() # Make it so the vector cannot be changed.
self._v = v # Store the vector.
def vector(self):
r'Return a vector representing this point.'
return self._v
def is_infinite(self):
'Return True if this point is on the line at infinity in the projective plane.'
return self._v[2] == 0
def plane(self):
'Return the ProjectivePlane containing this point'
return self._plane
def x(self):
'Return the x-coordinate or infinity if the point lies at infinity.'
if self.is_infinite():
return Infinity
return self._v[0] / self._v[2]
def y(self):
'Return the y-coordinate or infinity if the point lies at infinity.'
if self.is_infinite():
return Infinity
return self._v[1] / self._v[2]
def __repr__(self):
return f'ProjectivePoint({repr(self._plane)}, {repr(self._v)})'
def __str__(self):
if self.is_infinite():
if self._v[0] != 0:
m = self._v[1] / self._v[0]
return f'common point at infinity of lines of slope {m}'
else:
return 'common point at infinity of vertical lines'
return f'({self.x()}, {self.y()})'
def __eq__(self, other):
if not isinstance(other, ProjectivePoint):
return False
if self._v[0] != 0:
ratio = other._v[0] / self._v[0]
elif self._v[1] != 0:
ratio = other._v[1] / self._v[1]
else:
# It is not allowed for all 3 entries to be zero
ratio = other._v[2] / self._v[2]
return ratio*self._v == other._v
def __hash__(self):
# cv will be the canonical representative of the equivalence class of self._v
if self._v[0] != 0:
cv = (1/self._v[0]) * self._v
elif self._v[1] != 0:
cv = (1/self._v[1]) * self._v
else:
cv = (1/self._v[2]) * self._v
# So we can hash cv, we set it to be immutable:
cv.set_immutable()
return hash((ProjectivePoint, cv))
Directions¶
Modify the classes above and add new classes so that each guideline below is satisfied. Each guideline has tests which should be passed.
Under each guideline, please create a revised version of the class discussed in the guideline. For classes that already exist, you should cut and paste the most recent version and revise the class before testing.
Test results will be printed differently depending if the test passed or failed.
def _pass(s):
s = s.replace('\n','<br/>')
from IPython.display import HTML
display(HTML(f'<span style="color: darkgreen; ">{s}</span>'))
_pass('Results of a successful test will be printed like this.')
def _fail(s, stop=True):
s = s.replace('\n','<br/>')
from IPython.display import HTML
display(HTML(f'<span style="color: red; font-weight: bold; ">{s}</span>'))
if stop:
raise ValueError('You failed a test')
_fail('''Results of a failed test will be printed like this.
Typically a ValueError will also be raised, causing futher execution to stop.''', stop=False)
Important: When you make changes to a class, you must rerun the code block containing the class and recreate any objects to be sure it is working correctly.
Guideline 1: Make creating points more convienient¶
Add a point
method to the ProjectivePlane
class. The class should take as input a triple of elements of the base field. It should return a ProjectivePoint
in the plane that represents the equivalence class of the corresponding vector in $F^3$ (where $F$ is the base field). Then given a plane, you should be able to construct a point with
plane.point([1, 2, 3])
# Insert an updated ProjectivePoint class here.
# Tests
rational_plane = ProjectivePlane(QQ)
print(f'rational_plane = {rational_plane}')
if not hasattr(rational_plane, 'point') or not callable(rational_plane.point):
_fail('The plane `rational_plane` does not have a `point` method.')
p = rational_plane.point([1,2,3])
print(f'p = {p}')
if not isinstance(p, ProjectivePoint):
_fail('The object produced by the `point` method is not a ProjectivePoint.')
if p.x() != 1/3 or p.y() != 2/3:
_fail('The wrong point was produced!')
if p.plane() != rational_plane:
_fail('The point was produced in the wrong plane.')
_pass('The tests were passed.')
Guideline 2: Create a ProjectiveLine class¶
Create a ProjectiveLine class above under the heading The projective line class
.
The class should have an __init__
which takes as input a ProjectivePlane
plane
and a normal vector n
. Both should be stored off as variables in self
(ideally the variable names should start with an underscore). Once this is done, you should be able to construct a ProjectiveLine
using the following syntax:
rational_plane = ProjectivePlane(QQ)
line = ProjectiveLine(rational_plane, [2, -3, -1])
You should be able to use for n
any object that can be converted into a non-zero vector in $F^3$, where $F$ is the underlying field. Note that you can get the vector space $F^3$ from a ProjectivePlane
via the call plane.vector_space()
.
If a value of n
is provided that represents the zero vector, then a ValueError
should be raised.
Hint: The __init__
method for ProjectiveLine
should be very similar to the __init__
method for ProjectivePoint
.
# Create a ProjectiveLine class here.
# Commands from above. These should execute without error:
rational_plane = ProjectivePlane(QQ)
line = ProjectiveLine(rational_plane, [2, -3, -1])
line
if not 'ProjectiveLine' in locals():
_fail('ProjectiveLine is not defined!')
if not isinstance(ProjectiveLine, type):
_fail('ProjectiveLine is not a class!')
algebraic_plane = ProjectivePlane(AA)
print(f'algebraic_plane = {algebraic_plane}')
line = ProjectiveLine(algebraic_plane, [1, sqrt(2), 3^(1/3)])
if not isinstance(line, ProjectiveLine):
_fail('`line` is not a ProjectiveLine!')
_pass('The test passed!')
Guideline 3: Add some "getter" methods¶
Add methods normal()
and plane()
to the ProjectiveLine
class.
The method plane()
should return the ProjectivePlane
that contains the line. You should be able to call it using the syntax line.plane()
.
The method normal()
should return a vector in $F^3$, where $F$ is the base field. This vector must be non-zero and perpendicular to the vectors making up equivalence classes representing points contained in the line. Given a line, you should be able to get its normal with the call line.normal()
.
# Copy and update the ProjectiveLine class here.
# Tests of ProjectiveLine.plane():
rational_plane = ProjectivePlane(QQ)
line = ProjectiveLine(rational_plane, [2, -3, -1])
if not hasattr(line, 'plane') or not callable(line.plane):
_fail('plane() is not a method of line.')
if line.plane() != rational_plane:
_fail('line.plane() should return rational_plane!')
_pass('The .plane() method seems to be working correctly!')
# Tests of ProjectiveLine.normal():
algebraic_plane = ProjectivePlane(AA)
line = ProjectiveLine(algebraic_plane, [1, 2, sqrt(2)])
if not hasattr(line, 'normal') or not callable(line.normal):
_fail('normal() is not a method of line.')
n = line.normal()
print(f'line.normal() returned {n}.')
if n not in VectorSpace(AA, 3):
_fail('line.normal() should return an element of VectorSpace(AA, 3).')
if n[1] != 2*n[0] or n[2] != sqrt(2)*n[0]:
_fail('line.normal() did not return a multiple of [1, 2, sqrt(2)].')
_pass('The .normal() method seems to be working correctly!')
Guideline 4: Add some geometric methods¶
Supposing a line in the projective plane is not the line at infinity, it is an extension of a line in the usual Euclidean plane $F^2$ obtained by adding one point at infinity. So for lines other than the line at infinity, standard properties of lines apply. You should be able to compute (by hand) a formula for points in the Euclidean plane that lie in a line in terms of the normal vector.
Add more methods as described below to the ProjectiveLine
class:
- A
line.is_at_infinite()
method that returnTrue
ifline
is the line at infinity andFalse
otherwise. (The line at infinity has $[0, 0, 1]$ as one of its normal vectors.) - A
line.slope()
method. If the line is not the line at infinity, it should return the slope of the line, which is either an element of the base field orInfinity
(in the vertical case). The method should raise aValueError
ifline
is the line at infinity. (For raising aValueError
check the syntax used in the__init__
method ofProjectivePoint
.) - A
line.y_intercept()
method that returns an element in the base fieldb
such that theProjectivePoint
$[0, b, 1]$ lies in the line. A value ofInfinity
should be returned if the line is vertical or is at infinity. AValueError
should be raised if the line is the y-axis. - A
line.x_intercept()
method that returns an element in the base fielda
such that theProjectivePoint
$[a, 0, 1]$ lies in the line. A value ofInfinity
should be returned if the line is horizontal or is at infinity. AValueError
should be raised if the line is the x-axis.
# Copy and update the ProjectiveLine class here.
# We setup some example lines for the tests
rational_plane = ProjectivePlane(QQ)
line1 = ProjectiveLine(rational_plane, [0, 0, 3/2])
line2 = ProjectiveLine(rational_plane, [4, 0, -5])
algebraic_plane = ProjectivePlane(AA)
line3 = ProjectiveLine(algebraic_plane, [1, 3, sqrt(2)])
line4 = ProjectiveLine(algebraic_plane, [0, sqrt(2), 1])
if not hasattr(line1, 'is_at_infinity') or not callable(line1.is_at_infinity):
_fail('line1 does not have the method is_at_infinity()')
if line1.is_at_infinity() != True:
_fail('line1 is at infinity but is_at_infinity() did not return True.')
if line2.is_at_infinity() != False:
_fail('line2 is not at infinity but is_at_infinity() did not return False.')
if line3.is_at_infinity() != False:
_fail('line3 is not at infinity but is_at_infinity() did not return False.')
if line4.is_at_infinity() != False:
_fail('line4 is not at infinity but is_at_infinity() did not return False.')
_pass('.is_at_infinity() seems to be working correctly')
if not hasattr(line1, 'slope') or not callable(line1.slope):
_fail('line1 does not have the method slope()')
try:
line1.slope()
_fail('line1 is at infinity, so .slope() should have raised a ValueError.')
except ValueError:
pass
if line2.slope() != Infinity:
_fail('line2 has slope `Infinity`, but your method did not return this.')
if line3.slope() != -1/3:
_fail('line3 has slope -1/3, but your method did not return this.')
if line4.slope() != 0:
_fail('line3 has slope 0, but your method did not return this.')
_pass('.slope() seems to be working correctly.')
if not hasattr(line1, 'y_intercept') or not callable(line1.y_intercept):
_fail('line1 does not have the method y_intercept()')
if line1.y_intercept() != Infinity:
_fail('line1 has y-intercept at infinity, but your method did not return `Infinity`.')
if line2.y_intercept() != Infinity:
_fail('line2 has y-intercept at infinity, but your method did not return `Infinity`.')
if line3.y_intercept() != AA(-sqrt(2)/3):
_fail('line3 has y-intercept -sqrt(2)/3, but your method did not return this.')
if line4.y_intercept() != AA(-sqrt(2)/2):
_fail('line4 has y-intercept -sqrt(2)/2, but your method did not return this.')
_pass('.y-intercept() seems to be working correctly.')
if not hasattr(line1, 'x_intercept') or not callable(line1.x_intercept):
_fail('line1 does not have the method x_intercept()')
if line1.x_intercept() != Infinity:
_fail('line1 has x-intercept at infinity, but your method did not return `Infinity`.')
if line2.x_intercept() != 5/4:
_fail('line2 has x-intercept 5/4, but your method did not return this.')
if line3.x_intercept() != AA(-sqrt(2)):
_fail('line3 has x-intercept -sqrt(2), but your method did not return this.')
if line4.x_intercept() != Infinity:
_fail('line4 has x-intercept at infinity, but your method did not return `Infinity`.')
_pass('.x-intercept() seems to be working correctly.')
Guideline 5: Create string methods¶
Add __str__
and __repr__
methods to the ProjectiveLine
class.
Remember that the __str__
method should return a human-readable string. You can use the work in the previous section to get geometric information about the line in order to decide what to return.
The __repr__
method should return something that allows the programmer to reconstruct the object. I think 'ProjectiveLine([x, y, z])
is good enough where $n=(x,y,z)$ is a normal vector.
# Copy and update the ProjectiveLine class here.
# Tests for the line at infinity
rational_plane = ProjectivePlane(QQ)
line = ProjectiveLine(rational_plane, [0, 0, 1])
rs = repr(line)
if 'object at' in rs:
_fail('repr(line) seems not to have been overriden ("object at" appears in it)')
print(f"The line at infinity is represented as '{rs}'")
ss = str(line)
if 'object at' in ss:
_fail('str(line) seems not to have been overridden ("object at" appears in it)')
print(f"The line at infinity appears as '{ss}' when converted to a string")
# Tests for the line at y = 2x-3
rational_plane = ProjectivePlane(QQ)
line = ProjectiveLine(rational_plane, [-2, 1, 3])
rs = repr(line)
if 'object at' in rs:
_fail('repr(line) seems not to have been overridden ("object at" appears in it)')
print(f"The line at infinity is represented as '{rs}'")
ss = str(line)
if 'object at' in ss:
_fail('str(line) seems not to have been overridden ("object at" appears in it)')
print(f"The line at infinity appears as '{ss}' when converted to a string")
# Tests for the line at y = sqrt(3)
algebraic_plane = ProjectivePlane(AA)
line = ProjectiveLine(algebraic_plane, [0, -1, sqrt(3)])
rs = repr(line)
if 'object at' in rs:
_fail('repr(line) seems not to have been overridden ("object at" appears in it)')
print(f"The line at infinity is represented as '{rs}'")
ss = str(line)
if 'object at' in ss:
_fail('str(line) seems not to have been overridden ("object at" appears in it)')
print(f"The line at infinity appears as '{ss}' when converted to a string")
# Tests for the line at 2*x = 7
rational_plane = ProjectivePlane(QQ)
line = ProjectiveLine(rational_plane, [2, 0, -7])
rs = repr(line)
if 'object at' in rs:
_fail('repr(line) seems not to have been overridden ("object at" appears in it)')
print(f"The line at infinity is represented as '{rs}'")
ss = str(line)
if 'object at' in ss:
_fail('str(line) seems not to have been overridden ("object at" appears in it)')
print(f"The line at infinity appears as '{ss}' when converted to a string")
Guideline 6: Create equals and hash methods¶
Add __eq__
and __hash__
methods to the ProjectiveLine
class. The equals mthod should work when comparing the line to any other object. Note that two lines are equal if their normal vectors are scalar multiples of one another.
# Copy and update the ProjectiveLine class here.
# We setup some example lines for the tests
rational_plane = ProjectivePlane(QQ)
line1 = ProjectiveLine(rational_plane, [0, 0, 3/2])
line2 = ProjectiveLine(rational_plane, [4, 0, -5])
algebraic_plane = ProjectivePlane(AA)
line3 = ProjectiveLine(algebraic_plane, [4, 0, -5])
line4 = ProjectiveLine(algebraic_plane, [0, 4, -5])
line5 = ProjectiveLine(algebraic_plane, [8, 0, -10])
if line2 != line3:
_fail('Error: line2 and line3 should be equal')
if line3 != line5:
_fail('Error: line3 and line5 should be equal')
if line1 == line2:
_fail('Error: line1 and line2 should be unequal')
if line3 == line4:
_fail('Error: line3 and line4 should be unequal')
try:
hash(line1)
except TypeError:
_fail('Error: Lines should be hashable!')
if len( {hash(line1), hash(line2)} ) != 2:
_fail('Error: There should be different hashes for line1 and line2.')
if len( {hash(line3), hash(line4), hash(line5)} ) != 2:
_fail('Error: There should be exactly two different hashes in {line3, line4, line5}.')
_pass('The equals and hash tests were passed.')
# A class that is hashable should not be changable.
# Here we try to change a line.
# We should fail!
rational_plane = ProjectivePlane(QQ)
line = ProjectiveLine(rational_plane, [4, 0, -5])
v = line.normal()
vv = copy(v)
try:
v[1] = 3
if line.normal() != vv:
_fail('Error: I was able to change the line by changing the vector returned by line.normal()')
except Exception:
_pass('Good: I was unable to change the line by editing the returned normal.')
Guideline 7: Testing membership¶
A point $[\mathbf v]$ should be contained in the plane $\ell_{\mathbf n}$ if the dot product $\mathbf n \cdot \mathbf v$ is zero. Implement the __contains__(self, item)
method in ProjectiveLine
. It should return True
if item
is a ProjectivePoint
that is contained in the line. It should return False
otherwise.
# Copy and update the ProjectiveLine class here.
# Test the line y = 2x -3
rational_plane = ProjectivePlane(QQ)
line = ProjectiveLine(rational_plane, [4, -2, -6])
points = [
rational_plane.point([0, -3, 1]), # index 0
rational_plane.point([2, -2, 2]), # index 1
rational_plane.point([3, 0, 2]), # index 2
rational_plane.point([1, 2, 0]), # index 3
]
for index, pt in enumerate(points):
try:
if not pt in line:
_fail(f'Error: The point {pt} should be in the line but is not. (index {index})')
except TypeError:
_fail(f'Received a TypeError when testing containment. Was __contains__ implemented?')
points = [
5, # index 0
[1, 2, 0], # index 1
rational_plane.point([1, 3, 0]), # index 2
]
for index, pt in enumerate(points):
if pt in line:
_fail(f'Error: The object {pt} should not be in the line but it is declared to be. (index {index})')
_pass('The tests of containment were passed.')
Guideline 8: Updating ProjectivePlane¶
Update the class ProjectivePlane
in two ways:
- Add a
line
method which takes as input a normal vector and returns the associatedProjectiveLine
. Callingplane.line(n)
should be the same as callingProjectiveLine(plane, n)
. - Update the
__contains__
method fromProjectivePlane
so that both aProjectivePoint
and aProjectiveLine
in the plane are declared to be in the plane.
# Copy and update the ProjectivePlane class here.
# Test construction and containment of lines
algebraic_plane = ProjectivePlane(AA)
if not hasattr(algebraic_plane, 'line') or not callable(algebraic_plane.line):
_fail('Error: The plane does not have the method `line`.')
line = algebraic_plane.line([1, 2, sqrt(3)])
print(f'line = {line}')
if not isinstance(line, ProjectiveLine):
_fail('Error: The object returned by .line() should be a ProjectiveLine')
if not line.plane() == algebraic_plane:
_fail('Error: The plane of the line should be `algebraic_plane`.')
if not line in algebraic_plane:
_fail('Error: The line should be in `algebraic_plane`.')
point = algebraic_plane.point([4, 1, sqrt(2)])
print(f'point = {point}')
if not point in algebraic_plane:
_fail('Error: The point should be in `algebraic_plane`.')
_pass('All tests involving the `line()` method and containment in the plane were passed.')
Guideline 9: Add a method to join two points to form a line¶
The join of two points in the projective plane is the line between them. Add a method join
to ProjectivePoint
that returns the line between two points. For example, if p
and q
are ProjectivePoint
s in the plane plane
, then p.join(q)
should return the line $\overline{pq}$. (There is a formula for the normal using the cross product; see above.)
Attempting to join a point to an equal point (e.g., calling p.point(p)
) should result in a ValueError
. (This likely will occur automatically because you end up passing zero to the line
constructor.)
# Copy and update the ProjectivePoint class here.
# Test join
rational_plane = ProjectivePlane(QQ)
origin = rational_plane.point([0, 0, 1])
e_1 = rational_plane.point([1, 0, 1])
e_2 = rational_plane.point([0, 1, 1])
point = rational_plane.point([4, -3, 5])
if not hasattr(point, 'join') or not callable(point.join):
_fail('Error: The point does not have the method `join`.')
line = origin.join(e_1)
if not line == rational_plane.line([0, 1, 0]):
_fail('The join of the origin with e_1 should be the x-axis')
line = e_2.join(origin)
if not line == rational_plane.line([1, 0, 0]):
_fail('The join of e_2 with the origin should be the y-axis')
line = e_1.join(e_2)
if not line == rational_plane.line([-1, -1, 1]):
_fail('The join of e_1 with e_2 should be the line where x + y = 1')
line = e_2.join(point)
if not line == rational_plane.line([4, 2, -2]):
_fail('The join of e_2 with point should be the line where y = -2x + 1')
try:
point.join(point)
_fail('Joining a point to itself should result in an error.')
except ValueError:
pass
_pass('All tests were passed')
Guideline 10: Add a method to intersect two lines¶
The intersection of two lines in the projective plane is their common point. This point exists as long as the two lines are unequal.
Add a method intersect
to ProjectiveLine
that that takes as input another line and returns the point they share. For example if line1
and line2
are lines in the same ProjectivePlane
, then line1.intersect(line2)
should return the point they have in common.
Attempting to intersect a line to an equal line should result in a ValueError
.
# Copy and update the ProjectiveLine class here.
# Test construction and containment of lines
algebraic_plane = ProjectivePlane(AA)
x_axis = algebraic_plane.line([0, 1, 0])
y_axis = algebraic_plane.line([2, 0, 0])
line_at_inf = algebraic_plane.line([0, 0, sqrt(3)])
line = algebraic_plane.line([3, -sqrt(7), 4])
if not hasattr(line, 'intersect') or not callable(line.intersect):
_fail('Error: line does not have the method `intersect()`.')
point = x_axis.intersect(y_axis)
if not point == algebraic_plane.point([0, 0, sqrt(2)]):
_fail('Error: the intersection of the x-axis and the y-axis should be the origin.')
point = line_at_inf.intersect(y_axis)
if not point == algebraic_plane.point([0, 1, 0]):
_fail('Error: the intersection of the line at infinity and the y-axis should be the '+
'common point at infinity of vertical lines.')
point = line.intersect(x_axis)
if not point == algebraic_plane.point([4, 0, -3]):
_fail('Error: the intersection of the x_axis with the line at infinity should be '+
'(-4/3, 0) in the Euclidean plane.')
_pass('All tests were passed')
Guideline 11: Verifying Desargue's Theorem in cases¶
Consider six named points in the projective plane, $a_1, a_2, b_1, b_2, c_1, c_2$.
We say the points are in persective centrally if the three lines $\overline{a_1 a_2}$, $\overline{b_1 b_2}$, and $\overline{c_1 c_2}$ all pass through a common point called the center of perspectivity (the lines are coincident).
We say the points are in persective axially if the three points $\overline{a_1 b_1} \cap \overline{a_2 b_2}$, $\overline{b_1 c_1} \cap \overline{b_2 c_2}$, and $\overline{c_1 a_1} \cap \overline{c_2 a_2}$ all lie on the same line called the axis of perspectivity (the three points are colinear).
Desargue's Theorem says that the six points are in persective centrally if and only if they are in in persective axially.
Write two functions (not methods) that take as input six points as above.
- The function
in_perspective_centrally
should returnFalse
if the points are not in perspective centrally and return the center of perspectivity if they are. - The function
in_perspective_axially
should returnFalse
if the points are not in perspective axially and return the axis of perspectivity if they are.
By default, when we convert an object to boolean using bool(obj)
, the result will be True
. (This behavior can be overridden with the __bool__
method of a class.) Consequently, Desargue's Theorem can be restated as saying that the following two boolean values are equal:
bool(in_perspective_centrally(a1, a2, b1, b2, c1, c2))
bool(in_perspective_axially(a1, a2, b1, b2, c1, c2))
# Write in_perspective_centrally and in_perspective_axially functions here.
# Here we test an example that is in perspective centrally
plane = ProjectivePlane(QQ)
p1 = plane.point([1, 0, 1])
q1 = plane.point([1, 1, 1])
r1 = plane.point([3, 2, 1])
p2 = plane.point([1, 2, 0])
q2 = plane.point([4, 5, 1])
r2 = plane.point([15, 26, 7])
pt = in_perspective_centrally(p1, p2, q1, q2, r1, r2)
if not pt:
_fail('Your function incorrectly indicated that these six points are not in perspective centrally.')
if pt != plane.point([5/2, 3, 1]):
_fail('Your function returned the wrong center of perspectivity.')
line = in_perspective_axially(p1, p2, q1, q2, r1, r2)
if not line:
_fail('Your function incorrectly indicated that these six points are not in perspective axially.')
if line != plane.line([3, -10, -13]):
_fail('Your function incorrectly indicated that these six points are not in perspective axially.')
_pass('Your functions were correct in both cases.')
# Here we test an example that is not in perspective centrally
plane = ProjectivePlane(QQ)
p1 = plane.point([2, 1, 0])
q1 = plane.point([3, 1, 2])
r1 = plane.point([1, 2, 4])
p2 = plane.point([1/2, 2, 0])
q2 = plane.point([4, 5/3, 1])
r2 = plane.point([15, 5, 7])
pt = in_perspective_centrally(p1, p2, q1, q2, r1, r2)
if pt:
_fail('Your function incorrectly indicated that these six points are in perspective centrally.')
line = in_perspective_axially(p1, p2, q1, q2, r1, r2)
if line:
_fail('Your function incorrectly indicated that these six points are in perspective axially.')
_pass('Your functions were correct in both cases.')